<script>
import * as echarts from 'echarts';
import VChart from "vue-echarts";
import {formatSize, ChartDateFormatter} from "./formatter.js";

class OneChartToolTipFormatter {
  constructor(series) {
    // value: [data0, data1, ..., data-n]
    this.timestampsToLineDatas = new Map();
    let indexes = new Array(series.length).fill(0);
    const getValue = function(seriesIndex) {
      let dataIndex = indexes[seriesIndex];
      return series[seriesIndex].data[dataIndex][1];
    };
    const getTimestamp = function(seriesIndex) {
      let dataIndex = indexes[seriesIndex];
      return series[seriesIndex].data[dataIndex][0];
    }
    const getNextTimestamp = function(seriesIndex) {
      let dataIndex = indexes[seriesIndex] + 1;
      return series[seriesIndex].data[dataIndex][0];
    }
    const isEnd = function(seriesIndex) {
      return indexes[seriesIndex] === series[seriesIndex].data.length - 1;
    }
    const timeEqual = function(a, b) {
      return Math.abs(a - b) < Number.EPSILON;
    }
    const pick = function() {
      let minimumIndexes = [];
      let minTimestamp = Number.MAX_VALUE;
      for (let i = 0; i < series.length; ++i) {
        if (isEnd(i)) {
          continue;
        }
        let ts = getNextTimestamp(i);
        if (timeEqual(ts, minTimestamp)) {
          minimumIndexes.push(i);
        } else if (ts < minTimestamp) {
          minTimestamp = ts;
          minimumIndexes = [i];
        } else {
          // do nothing
        }
      }
      if (minimumIndexes.length === 0) {
        return {};
      }
      return {timestamp: minTimestamp, pickedIndexes: minimumIndexes};
    };
    this.timestampsToLineDatas.set(getTimestamp(0).toFixed(3), series.map((ser, serIndex) => getValue(serIndex)));

    let picks = {timestamp: undefined, pickedIndexes: []};
    while ((picks = pick()).timestamp !== undefined) {
      for (let pickedIndex of picks.pickedIndexes) {
        indexes[pickedIndex]++;
      }
      this.timestampsToLineDatas.set(picks.timestamp.toFixed(3), series.map((ser, j) => getValue(j)));
    }
  }
}

class TooltipFormatter {
  constructor(option, xFormatter) {
    this.xFormatter = xFormatter;
    let chartIndexesToSeries = new Map();
    for (let series of option.series) {
      if (chartIndexesToSeries.has(series.xAxisIndex)) {
        chartIndexesToSeries.get(series.xAxisIndex).push(series);
      } else {
        chartIndexesToSeries.set(series.xAxisIndex, [series]);
      }
    }

    let chartIndexesToFormatter = new Map();
    for (let indexAndSeries of chartIndexesToSeries) {
      indexAndSeries[1].sort((a, b) => {
        let aIndex = getIndexByLineName(a.name);
        let bIndex = getIndexByLineName(b.name);
        if (aIndex < bIndex) {
          return -1;
        }
        if (aIndex > bIndex) {
          return 1;
        }
        return 0;
      });
      chartIndexesToFormatter.set(indexAndSeries[0], new OneChartToolTipFormatter(indexAndSeries[1]));
    }

    this.seriesIndexesToChartFormatter = new Map();
    for (let i = 0; i < option.series.length; ++i) {
      this.seriesIndexesToChartFormatter.set(i, chartIndexesToFormatter.get(option.series[i].xAxisIndex));
    }
  }
  format(params) {
    let timestamp = params[0].axisValue.toFixed(3);
    let values = this.seriesIndexesToChartFormatter.get(params[0].seriesIndex).timestampsToLineDatas.get(timestamp);
    if (values === undefined) {
      return `<p>Error: Failed to get values from time stamp ${timestamp}</p>`;
    }
    let tooltip = `<p>${this.xFormatter(timestamp)}</p>`;
    for (let i = 0; i < values.length; ++i) {
      let color = getColorByIndex(i);
      let name = getLineNameByIndex(i);
      // draw circle
      tooltip += `<p><span style="display:inline-block;margin-right:5px;border-radius:10px;width:9px;height:9px;background-color:${color}"></span>`
      tooltip += `${name} &emsp;&emsp; <b>${formatSize(values[i])}</b></p>`;
    }
    return tooltip;
  }
}

function mergeSizeLines(lines) {
  let timeAndDiffSizes = [];
  let lastSize = 0;
  let indexesToPos = [];
  let indexesToLastSize = [];
  for (let i = 0; i < lines.length; ++i) {
    indexesToPos.push(0);
    indexesToLastSize.push(0);
  }
  /**
   * return: [timestamp, diffSize]
   */
  let popMinByTime = function () {
    let minIndex = -1;
    for (let i = 0; i < lines.length; ++i) {
      if (indexesToPos[i] === lines[i].length) {
        continue;
      }
      if (minIndex < 0) {
        minIndex = i;
        continue;
      }
      if (lines[i][indexesToPos[i]][0] < lines[minIndex][indexesToPos[minIndex]][0]) {
        minIndex = i;
      }
    }
    if (minIndex < 0) {
      return undefined;
    }
    let timestamp = lines[minIndex][indexesToPos[minIndex]][0];
    let diffSize = lines[minIndex][indexesToPos[minIndex]][1] - indexesToLastSize[minIndex];
    indexesToLastSize[minIndex] = lines[minIndex][indexesToPos[minIndex]][1];
    indexesToPos[minIndex]++;
    return [timestamp, diffSize];
  };
  let minTimeElement = undefined;
  while ((minTimeElement = popMinByTime()) !== undefined) {
    let size = lastSize + minTimeElement[1];
    if (timeAndDiffSizes.length > 0
        && parseFloat(timeAndDiffSizes[timeAndDiffSizes.length - 1]) === parseFloat(minTimeElement[0])) {
      timeAndDiffSizes[timeAndDiffSizes.length - 1][1] = size;
    } else {
      timeAndDiffSizes.push([minTimeElement[0], size]);
    }
    lastSize = size;
  }
  return timeAndDiffSizes;
}
function getColorByIndex(index) {
  return ["black", "blue", "green"][index];
}
function getIndexByLineName(name) {
  return {
    totalMemSize: 0,
    allocatedMemSize: 1,
    borrowedMemSize: 2
  }[name];
}
function getLineNameByIndex(index) {
  return ["totalMemSize", "allocatedMemSize", "borrowedMemSize"][index];
}
function getColor(name) {
  return getColorByIndex(getIndexByLineName(name));
}

function getDateTimeFormatter(pd) {
  let timeFormatter = ChartDateFormatter.createByRange(pd.firstLogTimestamp, pd.latestLogTimestamp);
  return (timestamp) => timeFormatter.format(timestamp);
}

function commonChartData(pd, name) {
  return {
    title: {
      text: name
    },
    yAxis: {
      axisLabel: {
        formatter: formatSize
      }
    },
    xAxis: {
      axisLabel: {
        formatter: getDateTimeFormatter(pd)
      },
      min: pd.firstLogTimestamp,
      max: pd.latestLogTimestamp
    },
    series: []
  };
}

function commonEcChartOption() {
  return {
    title: [],
    xAxis: [],
    yAxis: [],
    grid: [],
    series: [],
    tooltip: {
      trigger: 'axis',
      valueFormatter: formatSize,
    },
    toolbox: {
      feature: {
        restore: {},
        saveAsImage: {}
      }
    },
    dataZoom: [
      {
        type: 'slider',
        xAxisIndex: [0, 1, 2, 3, 4, 5, 6],
      },
      {
        type: 'slider',
        yAxisIndex: [0, 1, 2, 3, 4, 5, 6],
        labelFormatter: formatSize
      }
    ]
  }
}
export default {
  name: "PoolCharts",
  props: [],
  components: {
    VChart
  },
  methods: {
    setLoading(isLoading) {
      this.loading = isLoading;
    },
    highlightBlock(blockId) {
      console.log("Highlight block ", blockId, ", pd ", this.pd);
    },
    updateByPd(pd) {
      let option = {};
      this.genStreamedChartsWithoutIndex(pd, option);
      this.option = this.genFormatter(pd, this.genLayout(option));
      /*
      this.option.series[3].markArea = {
        itemStyle: {
          color: 'rgba(255, 173, 177, 0.4)'
        },
        data: [
          [
            {
              name: 'Block',
              xAxis: 1696118489669.773
            },
            {
              xAxis: 1696118577459.198
            }
          ]
        ]
      }*/
    },
    genFormatter(pd, option) {
      option.dataZoom[0].labelFormatter = getDateTimeFormatter(pd);
      const tooltipFormatter = new TooltipFormatter(option, getDateTimeFormatter(pd));
      option.tooltip.formatter = (params) => tooltipFormatter.format(params);
      return option;
    },
    genLayout(option) {
      let chartsNum = option.dataByChart.length;
      let chartPercent = Math.floor(98 / chartsNum);
      let paddingPercent = Math.floor(chartPercent / 5);

      let ecOption = commonEcChartOption();
      for (let chartIndex = 0; chartIndex < option.dataByChart.length; ++chartIndex) {
        let oneOption = option.dataByChart[chartIndex];

        ecOption.title.push(Object.assign({
          top: String(chartIndex * chartPercent) + '%',
          left: 'center'
        }, oneOption.title));
        ecOption.grid.push(Object.assign({
          'top': String(chartIndex * chartPercent + paddingPercent) + '%',
          'bottom': String(100 - ((chartIndex + 1) * chartPercent) + paddingPercent) + '%',
        }, oneOption.grid));
        ecOption.xAxis.push(Object.assign({
          gridIndex: chartIndex,
        }, oneOption.xAxis));
        ecOption.yAxis.push(Object.assign({
          gridIndex: chartIndex,
          max: option.maxSize,
        }, oneOption.yAxis));
        for (let se of oneOption.series) {
          ecOption.series.push(Object.assign({
            xAxisIndex: chartIndex,
            yAxisIndex: chartIndex
          }, se));
        }
      }
      console.log("ecOption: ", ecOption);
      return ecOption;
    },
    /**
     *
     * @param pd
     * @param option
     */
    genStreamedChartsWithoutIndex(pd, option) {
      option.maxSize = 0;
      option.dataByChart = []
      let orderedStreamIds = [...(pd.streamIdsToMemporyPool.keys())];
      orderedStreamIds.sort();
      let namesToDatas = new Map();
      for (let streamId of orderedStreamIds) {
        let chartData = commonChartData(pd, 'stream ' + streamId);
        let seriesData = pd.streamIdsToMemporyPool.get(streamId).generateDataForApacheEcharts(pd.firstLogTimestamp, pd.latestLogTimestamp);
        for (let nameAndData of Object.entries(seriesData)) {
          chartData.series.push({
            type: 'line',
            name: nameAndData[0],
            data: nameAndData[1],
            lineStyle: {color: getColor(nameAndData[0])},
            itemStyle: {color: getColor(nameAndData[0])}
          });
          if (namesToDatas.has(nameAndData[0])) {
            namesToDatas.get(nameAndData[0]).push(nameAndData[1]);
          } else {
            namesToDatas.set(nameAndData[0], [nameAndData[1]]);
          }
        }
        option.dataByChart.push(chartData);
        option.maxSize = Math.max(option.maxSize,
                                  ...(seriesData.totalMemSize.map(e => e[1])),
                                  ...(seriesData.allocatedMemSize.map(e => e[1])),
                                  ...(seriesData.borrowedMemSize.map(e => e[1])))
      }
      if (orderedStreamIds.length > 1) {
        this.generateLevel1Pool(pd, namesToDatas, option);
      }
    },
    generateLevel1Pool(pd, namesToDatas, option) {
      let chartData = commonChartData(pd, "level 1");
      for (let nameAndDatas of namesToDatas) {
        let mergedData = mergeSizeLines(nameAndDatas[1]);
        chartData.series.push({
          type: 'line',
          name: nameAndDatas[0],
          data: mergedData,
          lineStyle: {color: getColor(nameAndDatas[0])},
          itemStyle: {color: getColor(nameAndDatas[0])}
        });
        let sizes = mergedData.map(e => e[1]);
        option.maxSize = Math.max(option.maxSize, ...sizes);
      }
      option.dataByChart.unshift(chartData);
    },
  },
  data() {
    return {
      option: {
        title: {
          textStyle: {
            color: "grey",
            fontSize: 20
          },
          text: "No data",
          left: "center",
          top: "center"
        },
      },
      pd: undefined,
      loading: false
    };
  }
}
</script>

<template>
  <div class="d-flex justify-content-center flex-column">
    <v-chart ref="chart" :option="option" :loading="loading" />
  </div>
</template>

<style scoped>

</style>